package toml

import (
	"fmt"
	"io/ioutil"
	"strconv"
	"strings"
	"time"
)

const (
	kindRoot    = 1
	kindSection = 2
	kindValue   = 3
	kindBool    = 4
	kindString  = 5
	kindInt     = 6
	kindFloat   = 7
	kindArray   = 8
	kindDate    = 9
)

type Parser struct{}

func (p Parser) parseKey(line string) (string, int) {
	index := strings.Index(line, "=")
	if index < 0 {
		return "", -1
	}
	return strings.Trim(line[0:index], " \t\n\r"), index
}

func (p Parser) Parse(tomlString string) Document {
	_ = fmt.Println

	output := newDocument()

	var currentValue *Node
	currentValue = nil
	currentSection := output.root
	lines := strings.Split(tomlString, "\n")
	for i := 0; i < len(lines); i++ {
		var line = strings.Trim(lines[i], " \t\n\r")
		if len(line) == 0 {
			continue
		}

		// COMMENT

		if line[0] == '#' {
			continue
		}

		// SECTION

		if line[0] == '[' && line[len(line)-1] == ']' {
			currentValue = nil
			path := line[1 : len(line)-1]
			names := strings.Split(path, ".")
			current := output.root
			for j := 0; j < len(names); j++ {
				name := names[j]
				node, ok := current.child(name)
				if !ok {
					section := newNodePointer()
					section.name = name
					section.kind = kindSection
					current.setChild(name, section)
					current = section
				} else {
					current = node
				}
				currentSection = current
			}
			continue
		}

		// VALUE

		key, index := p.parseKey(line)
		if index < 0 {
			if currentValue != nil {
				currentValue.value.raw += line
			} else {
				panic("Invalid value: " + line)
			}
		} else {
			node := newNodePointer()
			node.name = key
			node.value.raw = cleanRawValue(line[index+1:])
			node.kind = kindValue
			currentSection.setChild(key, node)
			currentValue = node
		}
	}

	output.root.loadValues()

	return output
}

func (p Parser) ParseFile(tomlFilePath string) Document {
	content, err := ioutil.ReadFile(tomlFilePath)
	if err != nil {
		panic(err.Error())
	}
	return p.Parse(string(content))
}

func cleanRawValue(s string) string {
	s = strings.Trim(s, " \t\n\r")
	if len(s) == 0 {
		return ""
	}

	inString := false
	escape := false
	for i := 0; i < len(s); i++ {
		c := s[i]
		if escape {
			escape = false
			continue
		}
		if c == '"' {
			inString = !inString
			continue
		}
		if inString && c == '\\' {
			escape = true
			continue
		}
		if c == '#' && !inString {
			return strings.Trim(s[0:i], " \t\n\r")
		}
	}

	return s
}

func parseValue(s string) (Value, int, bool) {
	var v Value
	if len(s) == 0 {
		return v, 0, false
	}

	if strings.Index(s, "true") == 0 || strings.Index(s, "false") == 0 {
		v.kind = kindBool
		v.asBool = strings.Index(s, "true") == 0
		index := 4
		if !v.asBool {
			index = 5
		}
		v.raw = s[0:index]
		return v, index, true
	}

	if s[0] == '"' {
		parsed, index, ok := parseString(s)
		if !ok {
			return v, 0, false
		}
		if parsed1, ok := parseDuration(parsed); ok {
			v.asDuration = parsed1
		}
		v.asString = parsed
		v.kind = kindString
		v.raw = s[0:index]
		return v, index, true
	}

	if s[0] == '[' {
		parsed, index, ok := parseArray(s)
		if !ok {
			return v, 0, false
		}
		v.asArray = parsed
		v.kind = kindArray
		v.raw = s[0:index]
		return v, index, true
	}

	if len(s) >= 20 && s[19] == 'Z' {
		parsed, index, ok := parseDate(s)
		if !ok {
			return v, 0, false
		}
		v.asDate = parsed
		v.kind = kindDate
		v.raw = s[0:index]
		return v, index, true
	}

	numString, index, ok := parseNumber(s)
	if !ok {
		return v, 0, false
	}

	parsedInt, err := strconv.ParseInt(numString, 10, 64)
	if err == nil {
		v.asInt = parsedInt
		v.kind = kindInt
		v.raw = s[0:index]
		return v, index, true
	}

	parsedFloat, err := strconv.ParseFloat(numString, 64)
	if err == nil {
		v.asFloat = parsedFloat
		v.kind = kindFloat
		v.raw = s[0:index]
		return v, index, true
	}

	return v, 0, false
}

func parseDate(s string) (time.Time, int, bool) {
	timeString := s[0:20]
	output, err := time.Parse(time.RFC3339, timeString)
	if err != nil {
		return output, 0, false
	}
	return output, 20, true
}

func parseDuration(s string) (time.Duration, bool) {
	output, err := time.ParseDuration(s)
	if err != nil {
		return output, false
	}
	return output, true
}

func parseNumber(s string) (string, int, bool) {
	numberString := ""
	allowedChars := "0123456789.-"
	for i := 0; i < len(s); i++ {
		c := s[i]
		if !strings.Contains(allowedChars, string(c)) {
			break
		}
		numberString += string(c)
	}
	if len(numberString) <= 0 {
		return "", 0, false
	}
	return numberString, len(numberString), true
}

func parseArray(s string) ([]Value, int, bool) {
	var output []Value
	if len(s) <= 0 {
		return output, 0, false
	}

	endIndex := 0
	state := 0 // 0 = start, 1 = before value, 2 = end value
	for i := 0; i < len(s); i++ {
		c := s[i]

		if state == 0 {
			if c != '[' {
				return output, endIndex, false
			}
			state = 1
			continue
		}

		if state == 1 {
			v, index, ok := parseValue(s[i:])
			if !ok {
				continue
			} else {
				output = append(output, v)
				i = i + index - 1
				state = 2
			}
		}

		if state == 2 {
			if c == ',' {
				state = 1
				continue
			}
			if c == ']' {
				endIndex = i + 1
				break
			}
		}
	}

	return output, endIndex, true
}

func parseString(s string) (string, int, bool) {
	if len(s) <= 0 {
		return "", 0, false
	}

	index := 0
	escape := false
	output := ""
	state := 0 // 0 = left, 1 = inside
	i := -1
	for _, c := range s {
		i++

		if state == 0 {
			if c != '"' {
				return "", 0, false
			}
			state = 1
			continue
		}

		if state == 1 {
			if escape {
				if c == '0' {
					output += "\x00"
				} else if c == 't' {
					output += "\t"
				} else if c == 'n' {
					output += "\n"
				} else if c == 'r' {
					output += "\r"
				} else if c == '"' {
					output += "\""
				} else if c == '\\' {
					output += "\\"
				} else {
					return "", 0, false // Or panic?
				}
				escape = false
				continue
			}

			if c == '\\' {
				escape = true
				continue
			}

			if c == '"' && !escape {
				index = i + 1
				break
			}

			output += string(c)
		}
	}

	return output, index, true
}
